<?php
/**
 * Created by PhpStorm.
 * User: zero
 * Date: 2018/6/23
 * Time: 10:11
 */

namespace app\index\controller;

//use app\facade\Redis;
use think\Controller;
use think\Exception;
use think\facade\Cache;
use think\facade\Env;
use think\Queue;


class Buy extends Controller
{
        private $canBuyTime;
        private $stock;
        private $cache;
        private  $handler;
        public function __construct()
        {
                parent::__construct();
                $this->stock = require_once Env::get('app_path') . '\data\stock.php';
                //定义开始秒杀时间
                $this->canBuyTime = 1530003600;
                //使用缓存高级句柄初始化
                $this->cache = Cache::init();
                $this->handler = $this->cache->handler();
                //调用自己绑定的Redis静态门脸,要使用可以到服务容器配置文件跟门脸类开启注释
                //Redis::connect('127.0.0.1', 6379);
        }

        public function index()
        {
                //当然这里可以通过$this->stock返回库存状态,前端判断到库存为0，抢购按钮置灰
                return view();
        }

        public function order()
        {
                //定义一个自己id 不重复验证,因为没后台,没填写随机生成
                $user = [];
                if (empty(input('myname'))) {
                        $myname = rand(1000, 2000);
                }
                $myname = input('myname');
                $user['name'] = $myname;
                //获取用户提交时间当前系统时间,作为判断是否可以开始秒杀的条件
                $time = time();
                $user['time'] = $time;
                if ($user['time'] < $this->canBuyTime) {
                        echo '暂时无法抢购，请在' . date('Y-m-d H:i:s', $this->canBuyTime) . '点之后抢购';
                }
                //如果可以已经可以抢购了，定义一个库存数量
                //首先生成模拟库存的数据库,路径为Env::get('app_path').'\data\stock.php';
                //定义下抢购规则:
                //1、我们让一个用户名只能成功抢购1次，如果已经抢过则直接结束
                //2、当没有库存则提示抢购失败
                //3、我们让参与抢购的队列只能有n个人进来 如果超过，则需要等有人出队，重新进来秒杀(降低服务器缓存压力)
                //抢购时先验证库存(这里只是初步验证),进入抢购时判断数据库数量是否大0，如果是进入队列处理
                if ($this->stock['stock'] > 0) {
                 //限制队列长度(缓存预热，限制缓存队列长度，有出才有进，不过这里属于第一次验证，还是会出现并发同时进入)
                 //直到进入队列后做第二次验证
                        if ($this->handler->lSize('queues:BuyingJobQueue') > 5) {
                                $this->error('队伍已满，请稍后重新秒杀', url('index'));
                                exit();
                        }
                        // 1.当前任务将由哪个类来负责处理。
                        //   当轮到该任务时，系统将生成一个该类的实例，并调用其 fire 方法
                        $jobHandlerClassName = 'app\index\job\Buying';
                        // 2.当前任务归属的队列名称，如果为新队列，会自动创建
                        $jobQueueName = "BuyingJobQueue";
                        // 3.当前任务所需的业务数据 . 不能为 resource 类型，其他类型最终将转化为json形式的字符串
                        //   ( jobData 为对象时，需要在先在此处手动序列化，否则只存储其public属性的键值对)
                        //传用户名到队列 如果秒杀成功则写入buyuser表
                        $jobData =  $user;
                        $isPushed = Queue::push($jobHandlerClassName, $jobData, $jobQueueName);
                        // redis 驱动时，返回值为 随机字符串|false
                        if ($isPushed !== false) {
                                echo "进入排队成功...等待秒杀结果<br>";
                        } else {
                                echo '排队失败';
                        }

                } else {
                        echo '抢购结束';
                        exit();
                }

                $this->assign('user', $user);
                return view();
        }

        //秒杀结果通过长轮询返回
        //在这里还要给用户一个返回结果的状态
        //验证状态status 1：队列满|2：没库存|3:用户已经存在|4：秒杀成功
        public function orderResult()
        {
                $username = input('user');
                if (!$username) exit();
                $count = input('count');
                if(empty($count) && $count>10 && $count<=0){
                        exit();
                }
                //设置程序执行时间
                set_time_limit(0);
                //获取用户数据库
                $users = require_once Env::get('app_path') . '\data\buyuser.php';
                $key = $username.'ikey';
                //检测缓存是否为空
                empty($this->cache->get($key))? $i = 0:$i = $this->cache->get($key);
                //只有在第一次请求才验证是否秒杀过的用户，并将状态设置为3,其他多次请求均跳过该验证
                if ($i==0 && in_array($username,$users)) {
                        $arr = ['code' => 20001, 'type' => 'error','status'=> "3"];
                        return json($arr, 200);
                }
                //获取前端轮询次数
                //验证轮询最长次数，防止前端搞事无限长连接服务器

                       //延时2秒进行长连接，避免已经成功的用户判断时未写入缓存
                while (true) {
                                 $status = $this->cache->get($username);
                                if (in_array($username,$users) && $this->handler->exists($username)) {
                                        $arr = ['code' => 10001, 'type' => 'success','status'=>$status];
                                        $this->handler->del($username);
                                        $this->handler->del($key);
                                        return json($arr, 200);
                                }else { // 模拟没有数据变化，将休眠 hold住连接
                                        $i++;
                                        $this->cache->set($key,$i);
                                        if($i == (int)$count){
                                                $arr = ['code' => 20001, 'type' => 'error','status'=>$status];
                                                $this->handler->del($key);
                                                $this->handler->del($username);
                                                return json($arr, 200);
                                        }else{
                                                //定义比前端超时时间更长的时间，hold住连接
                                                sleep(13);
                                                exit();
                                                }
                                         }
                                }

        }




}






